from collections import deque
from typing import List
import cv2 as cv
import numpy as np
import threading
import os,sys

# 机器视觉的图像分析遵循：采集 => 处理 => 分析 这三个部分
# 采集就是将图像从摄像头采集下来
# 处理就是进行转HSV，二值化，膨胀收缩等步骤，分析就是从中获取需要的数据
# 分析就是最后从图像的特征得到所需要的信息

previewImages = {}

class ImageQueue(deque):
    
    def __init__(self,
                 maxlen: int
                 ):
        super().__init__(self,maxlen=maxlen)
    def empty(self):
        return len(self) == 0
    def put(self,image):
        self.appendleft(image)
    def get(self):
        if self.empty():
            return None
        else:
            return self.pop()

class ImageCapture():
    
    def __init__(self,
                 maxBufferSize = 5,
                 ):
        self.threadLock = threading.Lock()
        self.thread = threading.Thread(target=self.captureLoop)
        self.outputQueue = None
        self.isRunning = False
        self.isConnected = False
    
    def connect(self,
                outputQueue: ImageQueue
                ):
        self.isConnected = True
        self.outputQueue = outputQueue

    def start(self):
        '''启动图像采集
        
        这个函数会开启图像采集的主循环，直到执行stop之后停止
        '''
        self.isRunning = True
        self.thread.start()
    
    def stop(self):
        '''停止图像采集
        
        这个函数会停止图像采集的主循环
        '''
        self.isRunning = False
        self.thread.join()
        self.outputQueue.clear()
    
    def captureLoop(self):
        '''图像采集的主循环

        这个函数不应当被外部执行，只会被内部的线程调用
        '''
        while self.isRunning:
            valid,image = self.captureTask()
            if valid: self.outputQueue.put(image)
    
    def captureTask(self):
        # 摄像头采集的任务
        # 必须返回一个元组，包括是否采集成功的布尔值和原图像
        # 这个函数其实是应该到时候被重载的
        return (False,None)

# 图像处理器
class ImageProcessor():
    
    def __init__(self,
                 name: str,
                 ):
        self.name = name
        self.requiredArgs = []
    def process(self,
                inputImage: np.ndarray,
                args: dict = {}
                ):
        # 处理后的图像，若不作处理可以直接传出原图像
        return inputImage

# 不需要再实现Analyzer，这里的设计已经变成了全部是ImageProcessor了
    
class ImagePreview(ImageProcessor):
    
    # 图像处理的预览界面，继承自处理器，但是直接将输入的图片按原样转发
    def __init__(self,
                 name: str,
                 ):
        super().__init__('ImagePreview_%s'%name)
        self.name = name
        self.previewImage = np.zeros((1,1,3),dtype=np.uint8)
        # cv.namedWindow(windowName)

    def process(self, inputImage, args):
        # cv.imshow(self.windowName,inputImage)
        # global previewImages
        self.previewImage = inputImage.copy()
        return inputImage

class ImagePipeline():
    def __init__(self,
                 capture: ImageCapture,
                 processors: List[ImageProcessor],
                 bufferSize: int = 5,
                 args: dict = {}
                 ):
        self.capture = capture
        self.captureQueue = ImageQueue(maxlen=bufferSize)
        self.processors = processors
        self.args = args
        self.pipelineThread = threading.Thread(target=self.runPipeline)
        self.result = None
        self.isRunning = False
        self.isSetup = False
        self.previewNames = []
        self.requiredArgs = []

    def runPipeline(self):
        while self.isRunning:
            if self.captureQueue.empty():
                continue
            result = self.captureQueue.get()
            for processor in self.processors:
                result = processor.process(result,self.args)
                if type(processor) == ImagePreview:
                    self.renderPreview(processor.name,processor.previewImage)
            self.result = result
        
    def setArgument(self,key,value):
        self.args[key] = value

    def setArguments(self,kvPair: dict):
        for key in list(kvPair.keys()):
            self.args[key] = kvPair[key]
    
    def getArgument(self,key):
        return self.args[key]
    
    def getArguments(self):
        return self.args
    def renderPreview(self,name:str, image:np.ndarray):
        pass

    def setup(self):
        self.capture.connect(self.captureQueue)
        for processor in self.processors:
            for arg in processor.requiredArgs:
                self.requiredArgs.append(arg)
            if type(processor) == ImagePreview:
                self.previewNames.append(processor.name)
        self.isSetup = True

    def start(self):
        if self.isSetup:
            self.isRunning = True
            self.capture.start()
            self.pipelineThread.start()

    def stop(self):
        self.capture.stop()
        self.isRunning = False
        self.isSetup = False
        self.pipelineThread.join()
        cv.destroyAllWindows()
        
        
    def getResult(self):
        return self.result
    